<?php

declare(strict_types=1);

use think\exception\HttpResponseException;
use think\Response;

/**
 * 助手函数文件，通过composer autoload 加载
 */


if (!function_exists('success')) {
    /**
     * 返回成功方法
     *
     * @param string $msg
     * @param array $data
     * @return \think\response\Json
     */
    function success($msg = '', $data = null)
    {
        $result = [
            'error' => 0,
            'msg' => $msg,
            'data' => $data
        ];

        return json($result);
    }
}

if (!function_exists('error')) {
    /**
     * 返回失败方法
     *
     * @param string $msg
     * @param array $data
     * @return \think\response\Json
     */
    function error($msg = '', $error_code = 1, $data = null, $status_code = 200)
    {
        $result = [
            'error' => $error_code,
            'msg' => $msg,
            'data' => $data
        ];

        return json($result, $status_code);

        // 抛异常方式
        // if ($error_code != 1 || $status_code !== 200) {
        //     throw (new SheepException)->setMessage($msg, $error_code, $status_code);
        // } else {
        //     throw new SheepException($msg);
        // }
    }
}

/**
 * 格式化经纬度
 */
if (!function_exists('match_latlng')) {
    /**
     * 格式化经纬度
     * @param string  $latlng    要格式化的经纬度
     * @return string
     */
    function match_latlng($latlng)
    {
        $match = "/^\d{1,3}\.\d{1,30}$/";
        return preg_match($match, $latlng) ? $latlng : 0;
    }
}


/**
 * 拼接查询距离 sql
 */
if (!function_exists('distance_builder')) {
    /**
     * 拼接查询距离 sql
     * @param string  $lat    纬度
     * @param string  $lng    经度
     * @return string
     */
    function distance_builder($lat, $lng)
    {
        return "ROUND(6378.138 * 2 * ASIN(SQRT(POW(SIN((" . match_latlng($lat) . " * PI() / 180 - latitude * PI() / 180) / 2), 2) + COS(" . match_latlng($lat) . " * PI() / 180) * COS(latitude * PI() / 180) * POW(SIN((" . match_latlng($lng) . " * PI() / 180 - longitude * PI() / 180) / 2), 2))) * 1000) AS distance";
    }
}



/**
 * 检测字符串是否是版本号
 */
if (!function_exists('is_version_str')) {
    /**
     * 检测字符串是否是版本号
     * @param string  $version
     * @return boolean
     */
    function is_version_str($version)
    {
        $match = "/^([0-9]\d|[0-9])(\.([0-9]\d|\d))+/";
        return preg_match($match, $version) ? true : false;
    }
}



if (!function_exists('is_url')) {
    function is_url($url)
    {
        if (preg_match("/^(http:\/\/|https:\/\/)/", $url)) {
            return true;
        }

        return false;
    }
}

/**
 * 获取所有应用
 */
if (!function_exists('get_apps')) {
    /**
     * 获取所有应用
     */
    function get_apps()
    {
        $finder = new \Symfony\Component\Finder\Finder();
        $finder->directories()->ignoreVCS(false)->depth('== 0')->in(root_path('app'));

        $apps = [];
        foreach ($finder as $dir) {
            $apps[] = $dir->getRelativePathname();
        }
        return $apps;
    }
}



/**
 * 快捷设置跨域请求头（跨域中间件失效时，一些特殊拦截时候需要用到）
 */
if (!function_exists('set_cors')) {
    /**
     * 快捷设置跨域请求头（跨域中间件失效时，一些特殊拦截时候需要用到）
     *
     * @return void
     */
    function set_cors() {
        $header = [
            'Access-Control-Allow-Origin' => '*',           // 规避跨域
            'Access-Control-Allow-Credentials' => 'true',
            'Access-Control-Max-Age'           => 1800,
            'Access-Control-Allow-Methods'     => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
            'Access-Control-Allow-Headers'     => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With, platform',
        ];
        // 直接将 header 添加到响应里面
        foreach ($header as $name => $val) {
            header($name . (!is_null($val) ? ':' . $val : ''));
        }

        if (request()->isOptions()) {
            // 如果是预检直接返回响应，后续都不在执行
            exit;
        }
    }
}


/**
 * 检测应用是否安装完毕
 */
if (!function_exists('check_installed')) {
    /**
     * 检测应用是否安装完毕
     * @param string  $root_path    应用根目录
     * @param boolean  $is_return    是否返回
     * @return void|boolean
     */
    function check_installed($root_path = '', $is_return = false)
    {
        $root_path = $root_path ?: root_path();

        $installLock = $root_path . 'sheep' . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'install.lock';
        $is_installed = false;
        if (file_exists($installLock)) {
            $is_installed = true;
        }

        if ($is_return) return $is_installed;

        if ($is_installed) {
            // 程序已安装
            if (is_installing()) {
                set_cors();
                throw (new \sheep\exception\SheepException)->setMessage('应用程序已安装。如果需要重新安装，请删除install.lock文件', 1000, 200);
            }
        } else {
            // 程序未安装
            if (!is_installing()) {
                // 不是安装流程
                if (request()->isCli()) {
                    $command_name = current_command('only_name');
                    $checkCommand = [      // 未安装时如下命令不可执行
                        'sheep:chat',
                        'sheep:help',
                        'sheep:install',        // sheep:install init 已经在 is_installing 时判断为安装命令
                    ];
                    if (in_array($command_name, $checkCommand)) {
                        // 命令行提示，请先安装
                        (new \think\console\Output)->writeln('应用程序未安装，请先运行 php think sheep:install 命令安装');
                        exit;
                    }
                } else {
                    // 未安装，跳转到安装流程
                    // return redirect('/admin/install')->send();
                    set_cors();
                    throw (new \sheep\exception\SheepException)->setMessage('应用程序未安装，请先根据文档进行安装', 1001, 200);
                }
            }
        }
    }
}



/**
 * 检测应用是否安装 composer
 */
if (!function_exists('check_vendor')) {
    /**
     * 检测应用是否安装 composer
     * @param string  $root_path    应用根目录
     * @param boolean  $is_return    是否返回
     * @return void|boolean
     */
    function check_vendor($root_path = '', $is_return = false)
    {
        // 项目根目录
        $root_path = $root_path ?: root_path();

        // 是否有 vendor
        $composerPath = $root_path . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'composer';
        if (!file_exists($composerPath)) {
            if ($is_return) return false;

            echo 'Missing vendor files, try running "composer install".' . PHP_EOL;
            exit(1);
        }

        if ($is_return) return true;
    }
}



/**
 * 拼接资源域名，当前域名或者对象存储域名
 */
if (!function_exists('domainurl')) {
    /**
     * 获取本地 public 资源的域名的地址
     * @param string  $url    资源相对地址
     * @param boolean $domain 是否显示域名 或者直接传入域名
     * @return string
     */
    function domainurl($url, $domain = true)
    {
        $regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i";
        $driver = config('filesystem.default');
        $domainurl = config('filesystem.disks.' . $driver . '.url');
        $url = preg_match($regex, $url) || ($domainurl && stripos($url, $domainurl) === 0) ? $url : $domainurl . $url;
        if ($domain && !preg_match($regex, $url)) {
            $domain = is_bool($domain) ? request()->domain() : $domain;
            $url = $domain . $url;
        }
        return $url;
    }
}


/**
 * 如果是命令行模式，获取当前命令行正在执行的命令
 */
if (!function_exists('current_command')) {
    /**
     * 获取当前命令
     * @param string $type  all 整个命令从桉树，only_name 仅获取命令名称
     */
    function current_command($type = 'all')
    {
        if (!request()->isCli()) {
            return null;
        }

        $argv = $_SERVER['argv'];
        array_shift($argv);

        return $type == 'only_name' ? ($argv[0] ?? null) : $argv;
    }
}


/**
 * 判断当前是否正在安装
 */
if (!function_exists('is_installing')) {
    /**
     * 判断当前是否在安装中
     */
    function is_installing()
    {
        if (request()->isCli()) {
            // 获取当前命令
            $command = current_command();
            $command_name = $command[0] ?? '';
            $param = $command[1] ?? '';
            if ($command_name == 'sheep:install' && (!$param || $param == 'init')) {
                // sheep:install [init] 为安装命令，其他都不是
                return true;
            }

            return false;
        } else {
            // 获取当前路由
            $url = request()->baseUrl();
            // 安装路由
            return in_array($url, [
                '/admin/install',
                '/admin/install/check',
                '/admin/install/install'
            ]) ? true : false;
        }
    }
}


/**
 * 获取 redis connection 配置
 */
if (!function_exists('get_redis_connection')) {
    /**
     * 获取 redis connection 配置
     * @param string $connection    redis connection 名称
     * @return array 
     */
    function get_redis_connection($connection = null)
    {
        // 获取 redis 配置
        $configRedis = config('redis');
        // 当前要获取的 connection
        $current = $connection ?: $configRedis['default'];

        // 获取 connection 配置
        $redisConnection = config('redis.connections.' . $current);

        // 配置的覆盖
        $redisConnection['host'] = $redisConnection['host'] ?? $configRedis['host'];
        $redisConnection['port'] = $redisConnection['port'] ?? $configRedis['port'];
        $redisConnection['password'] = $redisConnection['password'] ?? $configRedis['password'];
        $redisConnection['timeout'] = $redisConnection['timeout'] ?? $configRedis['timeout'];
        $redisConnection['persistent'] = $redisConnection['persistent'] ?? $configRedis['persistent'];

        return $redisConnection;
    }
}


/**
 * 验证自定义签名
 */
if (!function_exists('check_cus_key')) {
    /**
     * 验证自定义签名 boolean
     *
     * @return void
     */
    function check_cus_key($expire = 60)
    {
        $sign = request()->param('sign', '');
        $stamp = request()->param('stamp', 0);
        $signKey = request()->param('signKey', '');

        if (time() > $stamp + $expire) {     // 超过指定时间失效
            \think\facade\Log::error('phoneCapt-error: ' . json_encode([
                'current_time' => time(),
                'request_time' => $stamp,
            ]));
            return false;
        }

        $first = date('Ymd');
        $second = $stamp;

        $sign_str = \sheep\model\Config::getConfigField('basic.site.sign') . "-" . $first . "-" . $second;
        if (!empty($signKey)) {
            $sign_str = $sign_str . "-" . $signKey;
        }

        $newSign = md5($sign_str);
        if ($newSign != $sign) {        // 签名错误
            \think\facade\Log::error('phoneCapt-error: ' . json_encode([
                'sign_str' => $sign_str,
                'new_sign' => $newSign,
                'sign' => $sign,
            ]));
            return false;
        }

        return true;
    }
}



if (!function_exists('format_log_error')) {
    /**
     * 格式化记录日志，重要地方使用
     *
     * @param object $error
     * @param string $name
     * @param string $message
     * @return void
     */
    function format_log_error($error, $name = 'QUEUE', $message = '')
    {
        $logInfo = [
            "========== $name LOG INFO BEGIN ==========",
            '[ Message ] ' . var_export('[' . $error->getCode() . ']' . $error->getMessage() . ' ' . $message, true),
            '[ File ] ' . var_export($error->getFile() . ':' . $error->getLine(), true),
            '[ Trace ] ' . var_export($error->getTraceAsString(), true),
            "============================================= $name LOG INFO ENDED ==========",
        ];

        $logInfo = implode(PHP_EOL, $logInfo) . PHP_EOL;
        \think\facade\Log::error($logInfo);
    }
}



/**
 * 验证自定义签名
 */
if (!function_exists('get_nonce_str')) {
    /**
     * 验证自定义签名 boolean
     *
     * @return void
     */
    function get_nonce_str()
    {
        $license_key = config('app.license_key');
        $referer = request()->header('referer');
        $host = request()->host(true);
        if ($referer) {
            $hosts = parse_url($referer);
            $host = $hosts['host'] ?? ($hosts['path'] ?? $referer);
        }

        $timestamp = time();
        $nonce_str = md5($host . $license_key . $timestamp);

        return $nonce_str. '.' .$timestamp;
    }
}


if (!function_exists('has_redis')) {

    /**
     * 判断是否配置好了 redis
     *
     * @param boolean $is_exception 是否 抛出异常
     * @return boolean
     */
    function has_redis($is_exception = false, $cache = true)
    {
        $key = 'has_redis';
        if ($cache && cache()->store('file')->has($key)) {
            // 使用缓存，并且存在缓存
            return cache()->store('file')->get($key);
        }

        $error_msg = '';
        try {
            \sheep\facade\Redis::ping();
        } catch (\BadFunctionCallException $e) {
            // 缺少扩展
            $error_msg = $e->getMessage() ? $e->getMessage() : "缺少 redis 扩展";
        } catch (\RedisException $e) {
            // 连接拒绝
            \think\facade\Log::write('redis connection redisException fail: ' . $e->getMessage());
            $error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接失败";
        } catch (\Exception $e) {
            // 异常
            \think\facade\Log::write('redis connection fail: ' . $e->getMessage());
            $error_msg = $e->getMessage() ? $e->getMessage() : "redis 连接异常";
        }

        cache()->store('file')->set($key, ($error_msg ? false : true), 10);        // 保存缓存
        if ($error_msg) {
            if ($is_exception) {
                throw new \Exception($error_msg);
            } else {
                return false;
            }
        }

        return true;
    }
}


/**
 * 检测系统必要环境
 */
if (!function_exists('check_env')) {
    function check_env($need = [], $is_throw = true)
    {
        $need = is_string($need) ? [$need] : $need;

        // 检测是否安装浮点数运算扩展
        if (in_array('bcmath', $need)) {
            if (!extension_loaded('bcmath')) {
                if ($is_throw) {
                    new \sheep\exception\SheepException('请安装浮点数扩展 【bcmath】');
                } else {
                    return false;
                }
            }
        }

        // 检测是否安装了队列
        if (in_array('queue', $need)) {
            if (!class_exists(\think\Queue::class)) {
                if ($is_throw) {
                    new \sheep\exception\SheepException('请安装 【topthink/think-queue 队列扩展】');
                } else {
                    return false;
                }
            }
        }

        return true;
    }
}



if (!function_exists('diff_in_time')) {
    /**
     * 计算两个时间相差多少天，多少小时，多少分钟
     * 
     * @param mixed $first 要比较的第一个时间 Carbon 或者时间格式
     * @param mixed $second 要比较的第二个时间 Carbon 或者时间格式
     * @param bool $format 是否格式化为字符串
     * @return string|array 
     */
    function diff_in_time($first, $second = null, $format = true, $simple = false)
    {
        $first = $first instanceof \Carbon\Carbon ? $first : \Carbon\Carbon::parse($first);
        $second = is_null($second) ? \Carbon\Carbon::now() : $second;
        $second = $second instanceof \Carbon\Carbon ? $second : \Carbon\Carbon::parse($second);

        $years = $first->diffInYears($second);
        $days = $first->diffInDays($second);
        $hours = $first->diffInHours($second);
        $minutes = $first->diffInMinutes($second);
        $second = $first->diffInSeconds($second);

        if (!$format) {
            return compact('years', 'days', 'hours', 'minutes', 'second');
        }

        $format_text = '';
        $start = false;
        if ($years) {
            $start = true;
            $format_text .= $years . '年';
        }
        if ($start || $days) {
            $start = true;
            $format_text .= ($days % 365) . '天';
        }

        if ($start || $hours) {
            $start = true;
            $format_text .= ($hours % 24) . '时';
        }
        if ($start || $minutes) {
            $start = true;
            $format_text .= ($minutes % 60) . '分钟';
        }
        if (($start || $second) && !$simple) {
            $start = true;
            $format_text .= ($second % 60) . '秒';
        }

        return $format_text;
    }
}


if (!function_exists('get_sn')) {
    /**
     * 获取唯一编号
     *
     * @param mixed $id       唯一标识
     * @param string $type    类型
     * @return string
     */
    function get_sn($id, $type = '') 
    {
        $id = (string)$id;

        $rand = $id < 9999 ? mt_rand(100000, 99999999) : mt_rand(100, 99999);
        $sn = date('Yhis') . $rand;

        $id = str_pad($id, (24 - strlen($sn)), '0', STR_PAD_BOTH);

        return $type . $sn . $id;
    }
}


if (!function_exists('string_hide')) {
    /**
     * 隐藏部分字符串
     *
     * @param string $string       原始字符串
     * @param int $start    开始位置
     * @return string
     */
    function string_hide($string, $start = 2, $end = 0, $hidden_length = 3) 
    {
        if (mb_strlen($string) > $start) {
            $hide = mb_substr((string)$string, 0, $start) . str_repeat('*', $hidden_length) . ($end > 0 ? mb_substr((string)$string, -$end) : '');
        } else {
            $hide = $string . '***';
        }

        return $hide;
    }
}


if (!function_exists('account_hide')) {
    /**
     * 隐藏账号部分字符串
     *
     * @param string $string       原始字符串
     * @param int $start    开始位置
     * @param int $end    开始位置
     * @return string
     */
    function account_hide($string, $start = 2, $end = 2) 
    {
        $hide = mb_substr((string)$string, 0, $start) . '*****' . mb_substr((string)$string, -$end);
        return $hide;
    }
}


if (!function_exists('client_unique')) {
    /**
     * 获取客户端唯一标识
     *
     * @return boolean
     */
    function client_unique()
    {
        $httpName = app('http')->getName();
        $url = request()->baseUrl();
        $ip = request()->ip();
        $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';

        $key = $httpName . ':' . $url . ':' . $ip . ':' . $user_agent;

        return md5($key);
    }
}




if (!function_exists('get_php')) {
    /**
     * 获取 php
     *
     * @param mixed $id       唯一标识
     * @param string $type    类型
     * @return string
     */
    function get_php() 
    {
        $phpBin = (new \Symfony\Component\Process\PhpExecutableFinder)->find();

        if ($phpBin === false) {
            // 如果都没有获取，尝试拼接宝塔 php 路径
            $phpBin = '/www/server/php/' . substr(str_replace('.', '', PHP_VERSION), 0, 2) . '/bin/php';
        } else {
            if (is_string($phpBin) && strpos(strtolower(PHP_OS), 'win') !== false) {
                // windows 上获取的 php 执行文件会是 php-cgi.exe，这里去掉 -cgi
                $phpBin = str_replace('-cgi', '', $phpBin);
            }
        }

        return $phpBin;
    }
}


if (!function_exists('is_throttle')) {
    /**
     * 判断路由是否需要限流控制
     *
     * @return boolean
     */
    function is_throttle() 
    {
        $noThrottleUris = config('throttle.no_throttle_uris');

        $url = request()->baseUrl();
        foreach ($noThrottleUris as $uri) {
            if (strpos($uri, ':') !== false) {
                if (strpos($url, strstr($uri, ':', true)) !== false) {
                    return false;
                }
            } else {
                if ($url == $uri) {
                    return false;
                }
            }
        }

        return true;
    }
}


if (!function_exists('str_before')) {
    /**
     * Get the portion of a string before the first occurrence of a given value.
     *
     * @param  string  $subject
     * @param  string  $search
     * @return string
     */
    function str_before($subject, $search)
    {
        if ($search === '') {
            return $subject;
        }

        $result = strstr($subject, (string) $search, true);

        return $result === false ? $subject : $result;
    }
}



if (!function_exists('str_after')) {
    /**
     * Return the remainder of a string after the first occurrence of a given value.
     *
     * @param  string  $subject
     * @param  string  $search
     * @return string
     */
    function str_after($subject, $search)
    {
        return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
    }
}



if (!function_exists('var_export_short')) {

    /**
     * 使用短标签打印或返回数组结构
     * @param mixed   $data
     * @param boolean $return 是否返回数据
     * @return string
     */
    function var_export_short($data, $return = true)
    {
        $replaced = [];
        $count = 0;

        //判断是否是对象
        if (is_resource($data) || is_object($data)) {
            return var_export($data, $return);
        }

        $dump = var_export($data, true);

        $dump = preg_replace('#(?:\A|\n)([ ]*)array \(#i', '[', $dump); // Starts
        $dump = preg_replace('#\n([ ]*)\),#', "\n$1],", $dump); // Ends
        $dump = preg_replace('#=> \[\n\s+\],\n#', "=> [],\n", $dump); // Empties
        $dump = preg_replace('#\)$#', "]", $dump); //End
        $dump = preg_replace('#\d+\s(=>)\s#', "", $dump); // Clear numbers keys

        if ($return === true) {
            return $dump;
        } else {
            echo $dump;
        }
    }

    if (!function_exists('sheep_config')) {
        /**
         * 获取SheepAdmin配置
         * @param string  $code    配置名
         * @return string
         */
        function sheep_config(string $code, $cache = true)
        {
            return \sheep\model\Config::getConfigs($code, $cache);
        }
    }

    if (!function_exists('gen_random_str')) {
        /**
         * 随机生成字符串
         * @param int  $length    字符串长度
         * @return bool $upper 默认小写
         */
        function gen_random_str($length = 10, $upper = false)
        {
            if ($upper) {
                $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
            } else {
                $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
            }

            $randomString = '';
            for ($i = 0; $i < $length; $i++) {
                $randomString .= $characters[rand(0, strlen($characters) - 1)];
            }
            return $randomString;
        }
    }
}
